From d549c7550d106823db58dab41ef4b5ab318dcf40 Mon Sep 17 00:00:00 2001 From: "kaf24@firebug.cl.cam.ac.uk" Date: Sat, 4 Jun 2005 17:35:19 +0000 Subject: [PATCH] bitkeeper revision 1.1670 (42a1e657B8Qurf6JEQIMURchxp90FA) Interrupt-driven serial transmit for 8250/16550 UARTs. Signed-off-by: Keir Fraser --- xen/arch/x86/cdb.c | 5 ++ xen/arch/x86/domain.c | 4 ++ xen/arch/x86/traps.c | 1 + xen/drivers/char/console.c | 10 +++ xen/drivers/char/ns16550.c | 32 +++++++--- xen/drivers/char/serial.c | 126 +++++++++++++++++++++++++++++++++++-- xen/include/xen/console.h | 3 + xen/include/xen/serial.h | 37 ++++++++--- 8 files changed, 196 insertions(+), 22 deletions(-) diff --git a/xen/arch/x86/cdb.c b/xen/arch/x86/cdb.c index ff9b6af076..1c95245e22 100644 --- a/xen/arch/x86/cdb.c +++ b/xen/arch/x86/cdb.c @@ -358,6 +358,7 @@ __trap_to_cdb(struct cpu_user_regs *regs) local_irq_save(flags); watchdog_disable(); + console_start_sync(); /* Shouldn't really do this, but otherwise we stop for no obvious reason, which is Bad */ @@ -383,9 +384,13 @@ __trap_to_cdb(struct cpu_user_regs *regs) ASSERT(!local_irq_is_enabled()); } } + + console_end_sync(); watchdog_enable(); atomic_inc(&xendbg_running); + local_irq_restore(flags); + return 0; } diff --git a/xen/arch/x86/domain.c b/xen/arch/x86/domain.c index 66732d8995..ae4099a008 100644 --- a/xen/arch/x86/domain.c +++ b/xen/arch/x86/domain.c @@ -123,6 +123,9 @@ void machine_restart(char * __unused) safe_halt(); } + watchdog_disable(); + console_start_sync(); + local_irq_enable(); /* Ensure we are the boot CPU. */ @@ -174,6 +177,7 @@ void __attribute__((noreturn)) __machine_halt(void *unused) void machine_halt(void) { watchdog_disable(); + console_start_sync(); smp_call_function(__machine_halt, NULL, 1, 0); __machine_halt(NULL); } diff --git a/xen/arch/x86/traps.c b/xen/arch/x86/traps.c index 35940b0df8..9e9d82d130 100644 --- a/xen/arch/x86/traps.c +++ b/xen/arch/x86/traps.c @@ -205,6 +205,7 @@ asmlinkage void fatal_trap(int trapnr, struct cpu_user_regs *regs) }; watchdog_disable(); + console_start_sync(); show_registers(regs); diff --git a/xen/drivers/char/console.c b/xen/drivers/char/console.c index 6f6234c8d0..7d7cafeaf5 100644 --- a/xen/drivers/char/console.c +++ b/xen/drivers/char/console.c @@ -467,6 +467,16 @@ void console_force_lock(void) spin_lock(&console_lock); } +void console_start_sync(void) +{ + serial_start_sync(sercon_handle); +} + +void console_end_sync(void) +{ + serial_end_sync(sercon_handle); +} + void console_putc(char c) { serial_putc(sercon_handle, c); diff --git a/xen/drivers/char/ns16550.c b/xen/drivers/char/ns16550.c index ea094c843c..f52ea04ac2 100644 --- a/xen/drivers/char/ns16550.c +++ b/xen/drivers/char/ns16550.c @@ -101,16 +101,24 @@ static void ns_write_reg(struct ns16550 *uart, int reg, char c) static void ns16550_interrupt( int irq, void *dev_id, struct cpu_user_regs *regs) { - serial_rx_interrupt(dev_id, regs); + struct serial_port *port = dev_id; + struct ns16550 *uart = port->uart; + + if ( (ns_read_reg(uart, IIR) & 7) == 2 ) + serial_tx_interrupt(port, regs); + else + serial_rx_interrupt(port, regs); } -static void ns16550_putc(struct serial_port *port, char c) +static int ns16550_tx_empty(struct serial_port *port) { struct ns16550 *uart = port->uart; + return !!(ns_read_reg(uart, LSR) & LSR_THRE); +} - while ( !(ns_read_reg(uart, LSR) & LSR_THRE) ) - cpu_relax(); - +static void ns16550_putc(struct serial_port *port, char c) +{ + struct ns16550 *uart = port->uart; ns_write_reg(uart, THR, c); } @@ -150,6 +158,10 @@ static void ns16550_init_preirq(struct serial_port *port) /* Enable and clear the FIFOs. Set a large trigger threshold. */ ns_write_reg(uart, FCR, FCR_ENABLE | FCR_CLRX | FCR_CLTX | FCR_TRG14); + + /* Check this really is a 16550+. Otherwise we have no FIFOs. */ + if ( (ns_read_reg(uart, IIR) & 0xc0) == 0xc0 ) + port->tx_fifo_size = 16; } static void ns16550_init_postirq(struct serial_port *port) @@ -157,20 +169,19 @@ static void ns16550_init_postirq(struct serial_port *port) struct ns16550 *uart = port->uart; int rc; + serial_async_transmit(port); + uart->irqaction.handler = ns16550_interrupt; uart->irqaction.name = "ns16550"; uart->irqaction.dev_id = port; if ( (rc = setup_irq(uart->irq, &uart->irqaction)) != 0 ) printk("ERROR: Failed to allocate na16550 IRQ %d\n", uart->irq); - /* For sanity, clear the receive FIFO. */ - ns_write_reg(uart, FCR, FCR_ENABLE | FCR_CLRX | FCR_TRG14); - /* Master interrupt enable; also keep DTR/RTS asserted. */ ns_write_reg(uart, MCR, MCR_OUT2 | MCR_DTR | MCR_RTS); - /* Enable receive interrupts. */ - ns_write_reg(uart, IER, IER_ERDAI); + /* Enable receive and transmit interrupts. */ + ns_write_reg(uart, IER, IER_ERDAI | IER_ETHREI); } #ifdef CONFIG_X86 @@ -188,6 +199,7 @@ static struct uart_driver ns16550_driver = { .init_preirq = ns16550_init_preirq, .init_postirq = ns16550_init_postirq, .endboot = ns16550_endboot, + .tx_empty = ns16550_tx_empty, .putc = ns16550_putc, .getc = ns16550_getc }; diff --git a/xen/drivers/char/serial.c b/xen/drivers/char/serial.c index e8d75a343b..2116d8ef08 100644 --- a/xen/drivers/char/serial.c +++ b/xen/drivers/char/serial.c @@ -42,8 +42,8 @@ void serial_rx_interrupt(struct serial_port *port, struct cpu_user_regs *regs) fn = port->rx_hi; else if ( !(c & 0x80) && (port->rx_lo != NULL) ) fn = port->rx_lo; - else if ( (port->rxbufp - port->rxbufc) != RXBUFSZ ) - port->rxbuf[MASK_RXBUF_IDX(port->rxbufp++)] = c; + else if ( (port->rxbufp - port->rxbufc) != SERIAL_RXBUFSZ ) + port->rxbuf[MASK_SERIAL_RXBUF_IDX(port->rxbufp++)] = c; spin_unlock_irqrestore(&port->lock, flags); @@ -56,6 +56,71 @@ void serial_rx_interrupt(struct serial_port *port, struct cpu_user_regs *regs) spin_unlock_irqrestore(&port->lock, flags); } +void serial_tx_interrupt(struct serial_port *port, struct cpu_user_regs *regs) +{ + int i; + unsigned long flags; + + BUG_ON(!port->driver); + BUG_ON(!port->driver->tx_empty); + BUG_ON(!port->driver->putc); + + spin_lock_irqsave(&port->lock, flags); + + for ( i = 0; i < port->tx_fifo_size; i++ ) + { + if ( port->txbufc == port->txbufp ) + break; + port->driver->putc( + port, port->txbuf[MASK_SERIAL_TXBUF_IDX(port->txbufc++)]); + } + + spin_unlock_irqrestore(&port->lock, flags); +} + +static void __serial_putc(struct serial_port *port, char c) +{ + int i; + + if ( (port->txbuf != NULL) && !port->sync ) + { + /* Interrupt-driven (asynchronous) transmitter. */ + if ( (port->txbufp - port->txbufc) == SERIAL_TXBUFSZ ) + { + /* Buffer is full: we spin, but could alternatively drop chars. */ + while ( !port->driver->tx_empty(port) ) + cpu_relax(); + for ( i = 0; i < port->tx_fifo_size; i++ ) + port->driver->putc( + port, port->txbuf[MASK_SERIAL_TXBUF_IDX(port->txbufc++)]); + port->txbuf[MASK_SERIAL_TXBUF_IDX(port->txbufp++)] = c; + } + else if ( ((port->txbufp - port->txbufc) == 0) && + port->driver->tx_empty(port) ) + { + /* Buffer and UART FIFO are both empty. */ + port->driver->putc(port, c); + } + else + { + /* Normal case: buffer the character. */ + port->txbuf[MASK_SERIAL_TXBUF_IDX(port->txbufp++)] = c; + } + } + else if ( port->driver->tx_empty ) + { + /* Synchronous finite-capacity transmitter. */ + while ( !port->driver->tx_empty(port) ) + cpu_relax(); + port->driver->putc(port, c); + } + else + { + /* Simple synchronous transmitter. */ + port->driver->putc(port, c); + } +} + void serial_putc(int handle, char c) { struct serial_port *port = &com[handle & SERHND_IDX]; @@ -67,14 +132,14 @@ void serial_putc(int handle, char c) spin_lock_irqsave(&port->lock, flags); if ( (c == '\n') && (handle & SERHND_COOKED) ) - port->driver->putc(port, '\r'); + __serial_putc(port, '\r'); if ( handle & SERHND_HI ) c |= 0x80; else if ( handle & SERHND_LO ) c &= 0x7f; - port->driver->putc(port, c); + __serial_putc(port, c); spin_unlock_irqrestore(&port->lock, flags); } @@ -101,7 +166,7 @@ char serial_getc(int handle) if ( port->rxbufp != port->rxbufc ) { - c = port->rxbuf[MASK_RXBUF_IDX(port->rxbufc++)]; + c = port->rxbuf[MASK_SERIAL_RXBUF_IDX(port->rxbufc++)]; break; } @@ -201,6 +266,46 @@ void serial_force_unlock(int handle) struct serial_port *port = &com[handle & SERHND_IDX]; if ( handle != -1 ) port->lock = SPIN_LOCK_UNLOCKED; + serial_start_sync(handle); +} + +void serial_start_sync(int handle) +{ + struct serial_port *port = &com[handle & SERHND_IDX]; + unsigned long flags; + + if ( handle == -1 ) + return; + + spin_lock_irqsave(&port->lock, flags); + + if ( port->sync++ == 0 ) + { + while ( (port->txbufp - port->txbufc) != 0 ) + { + while ( !port->driver->tx_empty(port) ) + cpu_relax(); + port->driver->putc( + port, port->txbuf[MASK_SERIAL_TXBUF_IDX(port->txbufc++)]); + } + } + + spin_unlock_irqrestore(&port->lock, flags); +} + +void serial_end_sync(int handle) +{ + struct serial_port *port = &com[handle & SERHND_IDX]; + unsigned long flags; + + if ( handle == -1 ) + return; + + spin_lock_irqsave(&port->lock, flags); + + port->sync--; + + spin_unlock_irqrestore(&port->lock, flags); } void serial_init_preirq(void) @@ -229,8 +334,19 @@ void serial_endboot(void) void serial_register_uart(int idx, struct uart_driver *driver, void *uart) { + /* Store UART-specific info. */ com[idx].driver = driver; com[idx].uart = uart; + + /* Default is no transmit FIFO. */ + com[idx].tx_fifo_size = 1; +} + +void serial_async_transmit(struct serial_port *port) +{ + BUG_ON(!port->driver->tx_empty); + if ( !port->txbuf ) + port->txbuf = (char *)alloc_xenheap_pages(get_order(SERIAL_TXBUFSZ)); } /* diff --git a/xen/include/xen/console.h b/xen/include/xen/console.h index 024840c0ad..4295a41de6 100644 --- a/xen/include/xen/console.h +++ b/xen/include/xen/console.h @@ -22,4 +22,7 @@ void console_endboot(int disable_vga); void console_force_unlock(void); void console_force_lock(void); +void console_start_sync(void); +void console_end_sync(void); + #endif /* __CONSOLE_H__ */ diff --git a/xen/include/xen/serial.h b/xen/include/xen/serial.h index 73daa385f0..9a1dd60cf4 100644 --- a/xen/include/xen/serial.h +++ b/xen/include/xen/serial.h @@ -16,8 +16,12 @@ typedef void (*serial_rx_fn)(char, struct cpu_user_regs *); void serial_set_rx_handler(int handle, serial_rx_fn fn); /* Number of characters we buffer for a polling receiver. */ -#define RXBUFSZ 32 -#define MASK_RXBUF_IDX(_i) ((_i)&(RXBUFSZ-1)) +#define SERIAL_RXBUFSZ 32 +#define MASK_SERIAL_RXBUF_IDX(_i) ((_i)&(SERIAL_RXBUFSZ-1)) + +/* Number of characters we buffer for an interrupt-driven transmitter. */ +#define SERIAL_TXBUFSZ 16384 +#define MASK_SERIAL_TXBUF_IDX(_i) ((_i)&(SERIAL_TXBUFSZ-1)) struct uart_driver; @@ -25,10 +29,17 @@ struct serial_port { /* Uart-driver parameters. */ struct uart_driver *driver; void *uart; + /* Number of characters the port can hold for transmit. */ + int tx_fifo_size; + /* Transmit data buffer (interrupt-driven uart). */ + char *txbuf; + unsigned int txbufp, txbufc; + /* Force synchronous transmit. */ + int sync; /* Receiver callback functions (asynchronous receivers). */ serial_rx_fn rx_lo, rx_hi, rx; /* Receive data buffer (polling receivers). */ - char rxbuf[RXBUFSZ]; + char rxbuf[SERIAL_RXBUFSZ]; unsigned int rxbufp, rxbufc; /* Serial I/O is concurrency-safe. */ spinlock_t lock; @@ -40,9 +51,11 @@ struct uart_driver { void (*init_postirq)(struct serial_port *); /* Hook to clean up after Xen bootstrap (before domain 0 runs). */ void (*endboot)(struct serial_port *); - /* Put a char onto the serial line. */ + /* Transmit FIFO ready to receive up to @tx_fifo_size characters? */ + int (*tx_empty)(struct serial_port *); + /* Put a character onto the serial line. */ void (*putc)(struct serial_port *, char); - /* Get a char from the serial line: returns FALSE if no char available. */ + /* Get a character from the serial line: returns 0 if none available. */ int (*getc)(struct serial_port *, char *); }; @@ -76,13 +89,23 @@ void serial_puts(int handle, const char *s); char serial_getc(int handle); /* Forcibly prevent serial lockup when the system is in a bad way. */ +/* (NB. This also forces an implicit serial_start_sync()). */ void serial_force_unlock(int handle); +/* Start/end a synchronous region (temporarily disable interrupt-driven tx). */ +void serial_start_sync(int handle); +void serial_end_sync(int handle); + +/* + * Initialisation and helper functions for uart drivers. + */ /* Register a uart on serial port @idx (e.g., @idx==0 is COM1). */ void serial_register_uart(int idx, struct uart_driver *driver, void *uart); - -/* Driver helper function: process receive work in interrupt context. */ +/* Place the serial port into asynchronous transmit mode. */ +void serial_async_transmit(struct serial_port *port); +/* Process work in interrupt context. */ void serial_rx_interrupt(struct serial_port *port, struct cpu_user_regs *regs); +void serial_tx_interrupt(struct serial_port *port, struct cpu_user_regs *regs); /* * Initialisers for individual uart drivers. -- 2.30.2